///<reference path='../SharedWorker.d.ts' />

import { Injectable, OnDestroy, Inject, Optional, NgZone } from '@angular/core';
import { IContextBroker, IContext, CONTEXT_BROKER_LOGGER, PersitContext, IPersist } from 'p-elite-app-contextbroker';
import { Subject } from 'rxjs/Subject';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
import 'rxjs/operator';
import { ISubscription, Subscription } from 'rxjs/Subscription';
import { SHARED_WORKER_URL, USE_SHARED_WORKER } from './elite-context-broker';
import { EliteWorker } from "./elite-worker";

export class ThreadCrosContextBroker implements IContextBroker, OnDestroy {
    private worker: EliteWorker;
    private observersByContextName: Map<string, any> = new Map<string, any>();

    constructor(
        private zone: NgZone,
        workerUrl?: string,
        useSharedWorker?: boolean,
        private logger?: any) {
        this.initAndStartWorker(workerUrl, useSharedWorker);
    }

    registerProvider(context: IContext): void {
        this.worker.postMessage(context);
    }

    registerObservableProvider<T extends IContext>(contextClass: string, contextProvider: Observable<T>): Subscription {
        return contextProvider.subscribe((context: T) => {
            this.worker.postMessage(context);
        });
    }

    registerListener<T extends IContext>(contextClass: string, observer: Observer<T>): Subscription {
        let subject: Subject<T> = this.observersByContextName.get(contextClass);
        if (subject == null) {
            subject = new Subject<T>();
            this.observersByContextName.set(contextClass, subject);
        }
        return subject.subscribe(observer);
    }

    registerContextListner<T extends IContext>(contextClass: string): Observable<T> {
        let subject: Subject<T> = this.observersByContextName.get(contextClass);
        if (subject == null) {
            subject = new Subject<T>();
            this.observersByContextName.set(contextClass, subject);
        }
        return subject as Observable<T>;
    }

    private initAndStartWorker(workerUrl?: string, useSharedWorker?: boolean) {
        let url = workerUrl ? workerUrl : './js/ordinaryWorker.js';
        this.worker = new EliteWorker(url, useSharedWorker);
        this.worker.onmessage((e) => {
            const data = e.data;
            const contextClass: string = data.contextClass;
            const subject = this.observersByContextName.get(contextClass);
            if (subject != null) {
                this.zone.run(() => {
                    subject.next(data);
                });
            }
        });

        this.worker.onerror((event) => {
            let friendlyMsg: string;

            if (event)
                friendlyMsg = `${event.message} (${event.filename} : ${event.lineno})`;

            if (this.logger && this.logger.error) {
                this.logger.error(friendlyMsg);
            } else {
                throw new Error(friendlyMsg);
            }
        });

        this.worker.start();
    }

    cancelSubscription(subscription: Subscription, requestId?: string): void {
        if (!subscription || subscription.closed)
            return;

        let subKey: string;
        for (let itemKey in this.observersByContextName) {
            if (this.observersByContextName.get(itemKey) == subscription) {
                subKey = itemKey;
                break;
            }
        }

        subscription.unsubscribe();

        if (subKey) {
            this.observersByContextName.delete(subKey);
        }
    }

    getPersitContext<T extends IPersist>(contextClass: string): Observable<T> {
        let persitContextClass = 'PersitContext';
        this.worker.postMessage({ contextClass: persitContextClass, persitContextClass: contextClass });
        let subject: Subject<PersitContext> = this.observersByContextName.get(persitContextClass);
        if (subject == null) {
            subject = new Subject<PersitContext>();
            this.observersByContextName.set(persitContextClass, subject);
        }

        return (subject as any)
            .filter(context => context.persitContextClass === contextClass)
            .map(context => context.persistContext);
    }

    ngOnDestroy(): void {
        // clear observers
        if (this.observersByContextName) {
            this.observersByContextName.forEach(item => {
                let sub = <ISubscription>item;
                if (sub.closed)
                    return;

                sub.unsubscribe();
            });
            this.observersByContextName.clear();
        }

        this.worker.ngOnDestroy();
    }
}